﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.ServiceModel.Syndication;
using System.Text.RegularExpressions;
using System.Xml;
using Kyoh.App.Crawler.Plugin;
using Kyoh.Lib.Plugin;
using System.Runtime.InteropServices;

namespace Crawler.Seeker.Twitter
{
    //[ExportPlugin(typeof(ISeeker), "Twitter", "kyoh", "http://kyoh.net/Publish/Crawler/Seekers/Twitter/", Description = "")]
    [Guid("07E0E1DA-E809-4503-A0F4-7A2511916F70")]
    public class TwitterSeeker : ISeeker
    {
        /*user_timeline
     自分の過去3200件分のステータスから最大20件(count引数使用時は最大200件)を取得する。
     id, user_id, screen_name のいずれかの引数を指定すれば、そのユーザのステータスを取得できる
     URL: http://api.twitter.com/1/statuses/user_timeline.format
          http://api.twitter.com/1/statuses/user_timeline/id.format
       (format は xml, json, rss, atom のうちのいずれかを指定。
        xml または json 指定時は、取得結果に retweets は含まれない[include_rts引数を指定していない場合]。 rss または atom 指定時は、取得結果に retweets が含まれる)
     引数:
        id=ユーザID または id=スクリーン名  (オプション)
           指定した ID またはスクリーン名のユーザのステータスを取得する
           (id, user_id, screen_name のいずれの引数をも指定しない場合は、自分のステータスを取得する)
             例:
               http://api.twitter.com/1/statuses/user_timeline/12345.xml
                 ユーザID 12345 のステータスを XML 形式で取得する
               http://api.twitter.com/1/statuses/user_timeline/bob.json
                 スクリーン名 bob のステータスを JSON 形式で取得する
        user_id=ユーザID  (オプション)
           指定した ID のユーザのステータスを取得する
             例:
               http://api.twitter.com/1/statuses/user_timeline.xml?user_id=1401881
                 ユーザID 1401881 のステータスを XML 形式で取得する
        screen_name=スクリーン名  (オプション)
           指定したスクリーン名のユーザのステータスを取得する
             例:
               http://api.twitter.com/1/statuses/user_timeline.xml?screen_name=101010
                 スクリーン名 101010 のステータスを XML 形式で取得する
                 (この場合、101010 はユーザIDではなく、スクリーン名であることに注意)
        count=件数  (オプション)
          指定した件数分、ステータスを取得する。ただし、200 より大きな値は指定できない 
             例:
               http://api.twitter.com/1/statuses/user_timeline.xml?count=5
                 自分のステータスのうち最新の5件を XML 形式で取得する
        since=日時  (オプション)
          指定した日時以降に update されたステータスを取得する
          日時のフォーマットは RFC822(の「5.  日付と時刻仕様」) に従う
          なお、本オプションの代わりに http リクエストヘッダで
            If-Modified-Since
          を明示することで、日時を指定することもできる
             例:
               http://api.twitter.com/1/statuses/user_timeline.rss?since=Tue%2C+27+Mar+2007+22%3A55%3A48+GMT
                 2007年3月27日22時55分48秒GMT以降に update されたステータスを RSS 形式で取得する
        since_id=ステータスID  (オプション)
          指定したステータスIDより大きな値のIDのステータスを取得する (指定したIDは取得対象外)
             例:
               http://api.twitter.com/1/statuses/user_timeline.xml?since_id=12345
                 ステータスID 12345 以降のステータスを XML 形式で取得する
        max_id=ステータスID  (オプション)
          指定したステータスID以下の値のIDのステータスを取得する (指定したIDも取得対象内)
             例:
               http://api.twitter.com/1/statuses/user_timeline.xml?max_id=54321
                 ステータスID 54321 以前のステータスを XML 形式で取得する
        page=ページ番号 (オプション)
          (1ページを20件とみなしたときの)ページ番号を指定することで、過去の任意の発言を20件単位で取得する
          なお、この page オプションは、Twitter の負荷が高いとき等、一時的に使えないようにすることがある
          注意: page 引数を使って遡れるのは最大3200件前の発言(page=160)までである
             例:
               http://api.twitter.com/1/statuses/users_timeline.rss?page=3
                 API実行時点で41件目から60件目に相当するステータスを RSS 形式で取得する
        trim_user=true (または trim_user=t, trim_user=1) (オプション)
          本引数を指定すると、ステータス情報に含まれる(そのステータスの投稿者の)ユーザ情報をユーザID(数字)のみにする
            例:
               http://api.twitter.com/1/statuses/users_timeline.json?trim_user=true
                  ユーザ情報簡易表記版の users_timeline を JSON 形式で取得する
        include_rts=true (または include_rts=t, include_rts=1) (オプション)
          本引数を指定すると、retweet も取得対象になる。なお、本引数指定時は、trim_user=true を同時に指定した場合でも、retweet 情報中のユーザ情報は簡易表記版にはならない
            例:
               http://api.twitter.com/1/statuses/users_timeline.json?include_rts=true
                  retweet を含む users_timeline を JSON 形式で取得する
        include_entities=true (または include_entities=t, include_entities=1) (オプション)
          本引数を指定すると、ステータス情報に entities という情報を含むようにする。entities には、そのステータスに関連するメタデータが格納される。
          entities の詳細については "Tweet Entities" (http://dev.twitter.com/pages/tweet_entities) を参照のこと。
            例:
               http://api.twitter.com/1/statuses/users_timeline.json?include_entities=true
                  entities 情報付きの users_timeline を JSON 形式で取得する
        //*/

        #region ISeeker メンバー

        private static readonly Regex UserTimeLineRegex = new Regex(@"/1/statuses/user_timeline/(?<userId>\w+)\.atom");
        private static readonly Regex TweerIdRegex = new Regex(@"\d+$");
        static readonly Regex UriRegex = new Regex(@"(h?ttps?://[^\s　<>]+)");
        public bool Supports(IAnchor anchor)
        {
            if(!Uri.IsWellFormedUriString(anchor.Uri , UriKind.Absolute))
                return false;

            var anchorUri = new Uri(anchor.Uri, UriKind.Absolute);
            if(!string.Equals(anchorUri.Host ,"api.twitter.com", StringComparison.InvariantCultureIgnoreCase))
                return false;

            if(!UserTimeLineRegex.IsMatch(anchorUri.LocalPath))
                return false;

            return true;
        }

        public IEnumerable<string> GetPages(IAnchor anchor)
        {
            if (!Uri.IsWellFormedUriString(anchor.Uri, UriKind.Absolute))
                yield break;

            var anchorUri = new Uri(anchor.Uri, UriKind.Absolute);
            if (!string.Equals(anchorUri.Host, "api.twitter.com", StringComparison.InvariantCultureIgnoreCase))
                yield break;

            var match = UserTimeLineRegex.Match(anchorUri.LocalPath);
            if (!match.Success)
                yield break;

            anchor.Title = match.Groups["userId"].Value;

            // リクエストURIを作成する。
            var builder = new UriBuilder(anchorUri.Scheme, anchorUri.Host, anchorUri.Port, anchorUri.LocalPath);
            builder.Query = "?count=200";
            if (anchor.PieceId > 0)
                builder.Query += "&since_id=" + anchor.PieceId.ToString(CultureInfo.InvariantCulture);
            foreach (var page in GetPagesAtPage(anchor, builder.Uri))
                yield return page;
        }

        private static IEnumerable<string> GetPagesAtPage(IAnchor anchor, Uri uri)
        {
            XmlReader reader;
            SyndicationFeed feed;
            try
            {
                reader = XmlReader.Create(uri.AbsoluteUri);
                feed = SyndicationFeed.Load(reader);
            }
            catch
            {
                yield break;
            }
            using (reader)
            {
                if (feed == null)
                    yield break;
                var first = true;
                foreach (var item in feed.Items)
                {
                    if (string.IsNullOrEmpty(item.Id))
                        continue;

                    if (first)
                    {
                        var match = TweerIdRegex.Match(item.Id);
                        if (match.Success)
                            anchor.PieceId = long.Parse(match.Value);
                        first = false;
                    }
                    if (item.Title == null || string.IsNullOrEmpty(item.Title.Text))
                        continue;

                    var uriMatches = UriRegex.Matches(item.Title.Text);
                    foreach (var foundUri in uriMatches
                        .Cast<Match>()
                        .Where(m => m.Success)
                        .Select(m => m.Value))
                        yield return foundUri;
                }
            }
        }

        #endregion

        public string Author { get { return "kyoh"; } }
        public string Description { get { return string.Empty; } }
        public Uri Uri { get { return new Uri("http://kyoh.net/"); } }

        public object Preferences
        {
            get { return null; }
            set { }
        }
        public string Name { get { return "TwitterSeeker"; } }

        public void Dispose() { Dispose(true); }
        ~TwitterSeeker() { Dispose(false); }
        protected void Dispose(bool disposing) { }
    }
}
